
/**
 * lambda4js - lambda extension for Javascript
 *
 * @author mhoms
 */

function profileLambdaRender(args, body, deep) {
	var r = "function(" + args + "){var r" + deep + "=" + body + ";return r" + deep + ";}";
	r = r.replace("@", "arguments.callee");

	if (deep === 0) {
		r = "var r="+r+";r";
	}
	return r;
}

var LAMBDA_RENDERER = profileLambdaRender;


/**
 * converteix una expressiÃ³ lambda (String) a una funciÃ³ Javascript (String)
 * evaluable per <tt>eval(...)</tt>
 *
 * @param exp
 *            expressiÃ³ lambda (String)
 * @param deep
 *            opcional, indica profunditat de la recursiÃ³
 * @return funciÃ³ String evaluable en Js
 */
function parseLambda(exp, deep) {
	if (deep === undefined) {
		deep = 0;
	}
	var pos = exp.indexOf("=>");
	if (pos === -1) {
		exp = "=>" + exp;
		pos = 0;
	}
	var args = exp.substring(0, pos);
	var body = exp.substring(pos + 2, exp.length);

	if (body.indexOf("=>") !== -1) {
		body = parseLambda(body, deep + 1);
	}

	return LAMBDA_RENDERER(args, body, deep);
}

/**
 * evalua una expressiÃ³ lambda (String); admet expressions lambda (String) o bé
 * closures Js
 *
 * @param exp
 *            expressiÃ³ lambda (String)
 * @return el resultat de l'evaluaciÃ³ de l'expressiÃ³ lambda (String)
 */
function lambda(exp) {
	if (typeof(exp) === "function") {
		return exp;
	} else {
		return eval(parseLambda(exp));
	}
}


function fold(lexp, start, collection) {
	var func = lambda(lexp);
	var r = start;
	for (e in collection) {
		r = func(r, collection[e]);
	}
	return r;
}

function foldKeys(lexp, start, collection) {
	var func = lambda(lexp);
	var r = start;
	for (e in collection) {
		r = func(r, e);
	}
	return r;
}



function newCollection(collection) {
	if (collection instanceof Array) {
		return [];
	} else if (collection instanceof Object) {
		return {};
	} else {
		throw "expected array/map"; // TODO
	}
}

function map(lexp, collection) {
	var func = lambda(lexp);
	var r = newCollection(collection);
	for (e in collection) {
		r[e] = func(collection[e]);
	}
	return r;
}


function mapKeys(lexp, collection) {
	var func = lambda(lexp);
	var r = newCollection(collection);
	for (e in collection) {
		r[func(e)] = collection[e];
	}
	return r;
}

// remove-if, select-if

function selectIf(lexp, collection) {
	var func = lambda(lexp);
	var r = newCollection(collection);
	for (e in collection) {
		if (func(collection[e])) {
			if (collection instanceof Array) {
				r.push(collection[e]);
			} else {
				r[e] = collection[e];
			}
		}
	}
	return r;
}

function removeIf(lexp, collection) {
	var func = lambda(lexp);
	var r = newCollection(collection);
	for (e in collection) {
		if (!func(collection[e])) {
			if (collection instanceof Array) {
				r.push(collection[e]);
			} else {
				r[e] = collection[e];
			}
		}
	}
	return r;
}

function selectIfKey(lexp, collection) {
	var func = lambda(lexp);
	var r = newCollection(collection);
	for (e in collection) {
		if (func(e)) {
			if (collection instanceof Array) {
				r.push(collection[e]);
			} else {
				r[e] = collection[e];
			}
		}
	}
	return r;
}

function removeIfKey(lexp, collection) {
	var func = lambda(lexp);
	var r = newCollection(collection);
	for (e in collection) {
		if (!func(e)) {
			if (collection instanceof Array) {
				r.push(collection[e]);
			} else {
				r[e] = collection[e];
			}
		}
	}
	return r;
}



/**
 * enmapa cada element amb la clau resultant de lambda:v->k
 */
function enMap(lexp, collection) {
	var func = lambda(lexp);
	var r = {};
	for (e in collection) {
		var key = func(collection[e]);
		r[key] = collection[e];
	}
	return r;
}




//S = {2*x | x in [0..10], x*x>3}
//> seq { for x in 0..100 do
//  if x*x > 3 then yield 2*x } ;;



// "x;range(0,5);x*2;x*x>3"

function range(start, end) {
	var r = [];
	for (i = start; i <= end; i++) {
		r.push(i);
	}
	return r;
}

function Generator(exp) {

	this.parts = exp.split(";");

	this.funof = function(numPart) {
		if (numPart >= this.parts.length || this.parts[numPart] === "") {
			return undefined;
		}
		return lambda(this.parts[0] + "=>" + this.parts[numPart]);
	};

	this.variable = this.parts[0];
	this.set    = this.funof(1)();
	this.mapFun = this.funof(2);
	this.filFun = this.funof(3);

	this.index = 0;
	this.finished = false;

	this.next = function() {

		if (this.index == this.set.length) {
			this.finished = true;
			return undefined;
		}

		var value = this.set[this.index++];

		if (this.mapFun !== undefined) {
			value = this.mapFun(value);
		}

		if (this.filFun !== undefined && !this.filFun(value)) {
			value = this.next();
		}
		return value;
	};

}


function listOf(exp) {
	var iter = new Generator(exp);
	var r = [];
	for (v = iter.next(); v !== undefined; v = iter.next()) {
		r.push(v);
	}
	return r;
}
